package com.ruoyi.system.service;


import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.system.domain.vo.WxPayResultVO;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
import com.wechat.pay.contrib.apache.httpclient.exception.ValidationException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Base64;

/**
 * @ProjectName: back
 * @Package: com.ruoyi.system.service
 * @ClassName: WechatPayConfig
 * @Author: haiyue
 * @Description:
 * @Date: 2025/6/5 上午 9:29:39
 * @Version: 1.0
 */
@Component
public class WechatPayConfig {

    private static final Logger log = LoggerFactory.getLogger(WechatPayConfig.class);

    /**商户号*/
    @Value("${wx.pay.merchantId}")
    private String merchantId;
    /**商户证书序列号*/
    @Value("${wx.pay.merchantSerialNumber}")
    private String mchSerialNumber;
    /**API V3密钥*/
    @Value("${wx.pay.apiV3key}")
    private String apiV3Key;
    /**商户私钥路径*/
    @Value("${wx.pay.privateKeyPath}")
    private String privateKeyPath;

    private static Config instance;

    @Bean
    public Config getConfig() throws IOException {

        if (instance == null) {
            synchronized (WechatPayConfig.class) {
                if (instance == null) {
                    instance =  new RSAAutoCertificateConfig.Builder()
                            .merchantId(merchantId)
                            // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥，商户私钥会用来生成请求的签名
                            .privateKeyFromPath(privateKeyPath)
                            .merchantSerialNumber(mchSerialNumber)
                            .apiV3Key(apiV3Key)
                            .build();
                }
            }
        }
//        Resource resource = new ClassPathResource("apiclient_key.pem");
//
//        Config config =
//                new RSAAutoCertificateConfig.Builder()
//                        .merchantId(merchantId)
//                        // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥，商户私钥会用来生成请求的签名
//                        .privateKeyFromPath(resource.getFile().getAbsolutePath())
//                        .merchantSerialNumber(mchSerialNumber)
//                        .apiV3Key(apiV3Key)
//                        .build();
        return instance;
    }

    /**
     * 获取签名验证器
     */
    @Bean(name = "getVerifier")
    public com.wechat.pay.contrib.apache.httpclient.auth.Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
        log.info("wxConfig：获取签名验证器");

        File file = new File(privateKeyPath);

        InputStream inputStream = Files.newInputStream(file.toPath());
        //获取商户私钥
        PrivateKey privateKey = PemUtil.loadPrivateKey(inputStream);
        //获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
                new PrivateKeySigner(mchSerialNumber, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        return certificatesManager.getVerifier(merchantId);
    }

    /**
     * 签名验证
     */
    public final boolean notificationHandler(HttpServletRequest req, Verifier verifier, String body) {
        boolean result = false;
        try {
            // 构建request，传入必要参数
            NotificationRequest request = new NotificationRequest.Builder()
                    .withSerialNumber(req.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SERIAL))
                    .withNonce(req.getHeader(WechatPayHttpHeaders.WECHAT_PAY_NONCE))
                    .withTimestamp(req.getHeader(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP))
                    .withSignature(req.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE))
                    .withBody(body)
                    .build();
            com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
            // 验签和解析请求体
            Notification notification = handler.parse(request);
            log.info("wxConfig：签名验证 =========》{}", notification.toString());
            result = true;
        } catch (ValidationException | ParseException e) {
            log.error("wxConfig：exception message", e);
        }
        return result;
    }

    /**
     * 获取商户的私钥文件路径
     */
    private PrivateKey getPrivateKey(String filename) throws IOException {
        log.info("wxConfig：私钥路径为=========》{}", filename);
        ClassPathResource resource = new ClassPathResource(filename);
        InputStream inputStream = resource.getInputStream();
        return PemUtil.loadPrivateKey(inputStream);
    }

    public WxPayResultVO analyzingWxPayResult(String requestBody) throws Exception {
        WxPayResultVO wxPayResultVO = new WxPayResultVO();
        // 解析回调数据
        JSONObject result = JSONObject.parseObject(requestBody);
        JSONObject resource = result.getJSONObject("resource");

        // 解密资源数据
        String associatedData = resource.getString("associated_data");
        String nonceStr = resource.getString("nonce");
        String ciphertext = resource.getString("ciphertext");

        String plainText = decryptAES256GCM(apiV3Key, associatedData, nonceStr, ciphertext);

        JSONObject paymentResult = JSONObject.parseObject(plainText);

        // 获取订单信息
        String outTradeNo = paymentResult.getString("out_trade_no");
        String transactionId = paymentResult.getString("transaction_id");
        String tradeState = paymentResult.getString("trade_state");
        String bankType = paymentResult.getString("bank_type");
        String successTime = paymentResult.getString("success_time");
        wxPayResultVO.setOutTradeNo(outTradeNo);
        wxPayResultVO.setTransactionId(transactionId);
        wxPayResultVO.setTradeState(tradeState);
        wxPayResultVO.setBankType(bankType);
        wxPayResultVO.setSuccessTime(successTime);

        return wxPayResultVO;
    }


    /**
     * AES-256-GCM 解密方法
     * @param apiV3Key APIv3密钥
     * @param associatedData 附加认证数据
     * @param nonce 随机数
     * @param ciphertext 密文
     * @return 解密后的明文
     * @throws Exception 解密异常
     */
    private String decryptAES256GCM(String apiV3Key, String associatedData,
                                    String nonce, String ciphertext) throws Exception {
        // 1. 创建AES-256-GCM解密器
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        // 2. 生成密钥
        SecretKeySpec key = new SecretKeySpec(
                apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");

        // 3. 设置GCM参数(认证标签长度为128位，随机数)
        GCMParameterSpec spec = new GCMParameterSpec(
                128, nonce.getBytes(StandardCharsets.UTF_8));

        // 4. 初始化解密器
        cipher.init(Cipher.DECRYPT_MODE, key, spec);

        // 5. 设置附加认证数据
        cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));

        // 6. 执行解密并返回明文
        byte[] decryptedBytes = cipher.doFinal(
                Base64.getDecoder().decode(ciphertext));

        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}
